/* penwr.c */
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <limits.h>

#include "pcap.h"

#ifdef _WIN32
#include "Packet32.h"
#include <windowsx.h>
#include <ntddndis.h>
#else /* _WIN32 */
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <net/if.h>
#if defined(__APPLE__)
#include <ifaddrs.h>
#include <net/if_dl.h>
#endif /* __APPLE__ */
#endif /* _WIN32 */

/*------*
 * Main *
 *------*/

int main(int argc, char *argv[])
{
	struct slnode_s {
		long datasize;
		void *data;
		struct slnode_s *next;
	};

	int result = 0, i, last_switch_f =0, count =1, *param_int =NULL, file_index = -1;
	char *parg, *pend =NULL, ch, **param_str =NULL, *work_iface =NULL;
	unsigned char mac[12]/* hack with sscanf() */;
	pcap_if_t *alldevs, *d;
	char errbuf[PCAP_ERRBUF_SIZE];
	int found_f;
	pcap_t *device =NULL; /* wpcap output device */
#define WPCAP_IOCTL_BUFFSIZE	100 /* rather arbitrary value */
	FILE *input = NULL;
#ifdef _WIN32
	LPADAPTER adapter;
	PPACKET_OID_DATA OidData;
	ULONG IoCtlBuffLen;
#else /* _WIN32 */
	int s;
	struct ifreq reqbuf;
#endif /* _WIN32 */
	struct slnode_s *head = NULL, *pnode, *prev;
	unsigned long total;

	if (argc > 1) {
		for (i = 1; i < argc && file_index < 0; i++) {
			parg = argv[i];
			if (!parg) {
				continue;
			}
			if (!last_switch_f && *parg == '-') {
				if (!strcmp(parg++, "--")) {
					last_switch_f = 1;
					continue;
				}
				while (parg && (ch = *parg++)) {
					switch (ch) {
					case 'w' : param_str = &work_iface; goto loc_getargstr;
					case 'c' : param_int = &count; goto loc_getargint;
					default :	goto loc_showusage;
loc_getargint:
						if (param_int) {
							if (!*parg) {
								if (i + 1 < argc) {
									*param_int = atoi(argv[++i]);
								} else {
									goto loc_showusage;
								}
							} else {
								*param_int = strtol(parg, &pend, 10);
								if (parg == pend)
									goto loc_showusage;
								parg = pend;
							}
						}
						break;
loc_getargstr:
						if (param_str) {
							if (!*parg) {
								if (i + 1 < argc) {
									*param_str = argv[++i];
								} else {
									goto loc_showusage;
								}
							} else {
								*param_str = parg;
								parg = parg + strlen(parg);
							}
							if (!*param_str || !strlen(*param_str))
								goto loc_showusage;
						}
						break;
					}
				}
			} else if (file_index == -1) {
				file_index = i;
			} else {
				goto loc_showusage;
			}
		}
	} else {
loc_showusage:
		fprintf(stderr, "Usage: PENWR [<switches>] <file1> [<file2>..<fileN>]\n"
				"Switches:\n"
				"  -w <MAC addr>\tset working interface, as hh:hh:hh:hh:hh:hh\n"
				"  -c <count>\tnumber to repeat\n"
				);

		return (EINVAL); /* usage */
	}

	if (file_index < 0)
		goto loc_showusage;

	if (work_iface) {
		unsigned int inmac[6];
		if (sscanf(work_iface, "%x:%x:%x:%x:%x:%x",
				inmac, inmac + 1, inmac + 2, inmac + 3, inmac + 4, inmac + 5) == 6) {
			mac[0] = inmac[0]; mac[1] = inmac[1]; mac[2] = inmac[2];
			mac[3] = inmac[3]; mac[4] = inmac[4]; mac[5] = inmac[5];
		} else {
			goto loc_showusage;
		}
	}

	/* open working interface */
	if (pcap_findalldevs(&alldevs, errbuf) == -1) {
		fprintf(stderr, "** pcap_findalldevs() failed with: %s\n", errbuf);
		return (ENODEV);
	} else {
		for (found_f = 0, d = alldevs; d; d = d->next) {
#ifdef _WIN32
			adapter = PacketOpenAdapter(d->name);
			if (adapter) {
				IoCtlBuffLen = (sizeof (PACKET_OID_DATA) + WPCAP_IOCTL_BUFFSIZE);
				OidData =(PPACKET_OID_DATA) GlobalAllocPtr(GMEM_MOVEABLE | GMEM_ZEROINIT, IoCtlBuffLen);
				if (OidData) {
					OidData->Oid = OID_802_3_CURRENT_ADDRESS;
					OidData->Length = WPCAP_IOCTL_BUFFSIZE;
					if (PacketRequest(adapter, FALSE, OidData)) {
						if (work_iface) {
							if (OidData->Data[0] == mac[0] && OidData->Data[1] == mac[1] &&
								   OidData->Data[2] == mac[2] && OidData->Data[3] == mac[3] &&
								   OidData->Data[4] == mac[4] && OidData->Data[5] == mac[5])
								found_f = 1;
						} else {
						/* take the first */
						/* FIXME: select dynamically depending on target through IP Helper'
						 * GetBestInterface() */
							mac[0] = OidData->Data[0]; mac[1] = OidData->Data[1];
							mac[2] = OidData->Data[2]; mac[3] = OidData->Data[3];
							mac[4] = OidData->Data[4]; mac[5] = OidData->Data[5];
							found_f = 1;
						}
					}
					GlobalFreePtr(OidData);
				}
				PacketCloseAdapter(adapter);
			}
#elif defined(__APPLE__)
			struct ifaddrs *ifas, *ia;
			if (getifaddrs(&ifas) == 0) {
				for (ia = ifas; ia; ia = ia->ifa_next) {
					if (strcmp(d->name, ia->ifa_name) != 0)
						continue;
					if (ia->ifa_addr && ia->ifa_addr->sa_family == AF_LINK) {
						struct sockaddr_dl *sdl =(struct sockaddr_dl *)ia->ifa_addr;
						if (!work_iface || memcmp(mac, LLADDR(sdl), sdl->sdl_alen) == 0) {
							memcpy(mac, LLADDR(sdl), sdl->sdl_alen);
							found_f = 1;
							break;
						}
					}
				}
				freeifaddrs(ifas);
			} else {
				fprintf(stderr, "** getifaddrs() failed (%s)\n", strerror(errno));
			}
#else /* _WIN32, __APPLE__ */
			memset(&reqbuf, 0, sizeof (reqbuf));
			strcpy(reqbuf.ifr_name, d->name);
			s = socket(PF_INET, SOCK_DGRAM, 0);
			if (s != -1) {
				if (ioctl(s, SIOCGIFHWADDR, &reqbuf) != -1) {
					if (work_iface) {
						if ((unsigned char) reqbuf.ifr_hwaddr.sa_data[0] == mac[0] &&
							(unsigned char) reqbuf.ifr_hwaddr.sa_data[1] == mac[1] &&
							(unsigned char) reqbuf.ifr_hwaddr.sa_data[2] == mac[2] &&
							(unsigned char) reqbuf.ifr_hwaddr.sa_data[3] == mac[3] &&
							(unsigned char) reqbuf.ifr_hwaddr.sa_data[4] == mac[4] &&
							(unsigned char) reqbuf.ifr_hwaddr.sa_data[5] == mac[5])
							found_f = 1;
					} else {
						mac[0] = (unsigned char) reqbuf.ifr_hwaddr.sa_data[0];
						mac[1] = (unsigned char) reqbuf.ifr_hwaddr.sa_data[1];
						mac[2] = (unsigned char) reqbuf.ifr_hwaddr.sa_data[2];
						mac[3] = (unsigned char) reqbuf.ifr_hwaddr.sa_data[3];
						mac[4] = (unsigned char) reqbuf.ifr_hwaddr.sa_data[4];
						mac[5] = (unsigned char) reqbuf.ifr_hwaddr.sa_data[5];
						found_f = 1;
					}
				} else {
					/*fprintf(stderr, "** ioctl() failed (%s)\n", strerror(errno));*/
				}
				close(s);
			} else {
				fprintf(stderr, "** socket() failed (%s)\n", strerror(errno));
			}
#endif /* _WIN32, __APPLE__ */
			if (found_f)
				break;
		}

		if (found_f && d) {
			device = pcap_open_live(d->name, /* portion to capture, doesn't matter */64,
					/* promiscuous mode */0, /* read timeout */1000,
					/* FIXME: rework, alien stack will be used */errbuf);
			printf("data will be put on %02x:%02x:%02x:%02x:%02x:%02x\n",
				mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
		} else {
			fprintf(stderr, "** couldn't find device match for %s\n", work_iface);
		}
		pcap_freealldevs(alldevs);
	}

	if (!device) {
		fprintf(stderr, "** device not ready\n");
		return (ENODEV);
	}

	for (i = file_index; i < argc; i++) {
		pnode = malloc(sizeof (struct slnode_s));
		if (!pnode) {
			fprintf(stderr, "** malloc() failed on %lu bytes\n", sizeof (struct slnode_s));
			pcap_close(device);
			return (ENOMEM);
		}

		input = fopen(argv[i], "rb");
		if (!input) {
			fprintf(stderr, "** couldn't open %s (%s)\n", argv[i], strerror(errno));
			pcap_close(device);
			return (ENOENT);
		}
		fseek(input, 0, SEEK_END);
		pnode->datasize = ftell(input);
		fseek(input, 0, SEEK_SET);
		/* cap the data */
		pnode->datasize = (pnode->datasize < USHRT_MAX ? pnode->datasize : USHRT_MAX);

		pnode->data = malloc(pnode->datasize);
		if (!pnode->data) {
			fprintf(stderr, "** malloc() failed on %li bytes\n", pnode->datasize);
			fclose(input);
			pcap_close(device);
			return (ENOMEM);
		}

		if (fread(pnode->data, 1, pnode->datasize, input) != pnode->datasize) {
			fprintf(stderr, "** fread() failed with %s\n", strerror(errno));
			fclose(input);
			pcap_close(device);
			return (EIO);
		}

		fclose(input);

		pnode->next = NULL;

		if (!head) {
			head = pnode;
		} else {
			prev->next = pnode;
		}

		prev = pnode;
	}

	for (result =0, total =0, i =0; !result && i < count; i++) {
		for (pnode =head; pnode; total += pnode->datasize, pnode = pnode->next) {
			if (pcap_sendpacket(device, pnode->data, pnode->datasize)) {
				/* UNDONE: notify user */
				fprintf(stderr, "** pcap_sendpacket() failed (%s)\n", pcap_geterr(device));
				result = EIO;
				break;
			}
		}
	}

	if (!result) {
		printf("%lu byte(s) put on wire\n", total);
	}

	while (head) {
		pnode = head->next;
		free(head->data);
		free(head);
		head = pnode;
	}

	pcap_close(device);

	return (result);
}

/* end */
